package com.fc.happyteam.process;

import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import com.fc.happyteam.process.migrate.*;
import com.fc.happyteam.process.migrate.dialects.HsqlDialect;
import com.fc.happyteam.process.migrate.dialects.MySQLDialect;
import com.fc.happyteam.process.migrate.dialects.OracleDialect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;

/**
 * Created by Charles on 2017/7/21.
 * This is a background processor that handle DB schema increamental upgrade.
 */
class DbMigration implements DisposableBean, Runnable  {
    private Thread thread;
    private volatile boolean requireMigrate;
    private ArrayList<ConnectionParameters> _params;
    private String _versionTableName;
    private static final Logger LOG = LoggerFactory.getLogger(DbMigration.class);
    private MigrationProps _migrationProps;
    private ApplicationContext _context;

    /**
     * maps dialect names to a class.
     */
    public static final Map<String, Class<? extends Dialect>> DIALECTS = new HashMap<String, Class<? extends Dialect>>();
    static {
        DIALECTS.put( "mysql",  MySQLDialect.class );
        DIALECTS.put( "oracle", OracleDialect.class );
        DIALECTS.put( "hsql",   HsqlDialect.class );
    }

    DbMigration(MigrationProps props, ApplicationContext ctx){
        this.thread = new Thread(this);
        this._migrationProps = props;
        if(_migrationProps.getPerformMigrate()){
            requireMigrate = true;
        }else{
            requireMigrate = false;
        }
        _params = new ArrayList<ConnectionParameters>();
        _versionTableName = "db_version";
        _context = ctx;
        initParams();
        this.thread.start();
    }

    @Override
    public void run() {
        while(requireMigrate){
            System.out.println("start upgrade database----------------------");
            try {
                upgrade();
                requireMigrate = false;
            } catch (Exception e) {
                LOG.error("DB migration failed. Please check if you configured the migration properties in application.yml. Or if db scripts sit in /resources/db/upgrade folder?");
                e.printStackTrace();
            }
            System.out.println("end upgrade database----------------------");
        }
    }

    @Override
    public void destroy(){
        requireMigrate = false;
    }

    /**
     * upgrades the system, upgrading to the most recent version.
     * @throws SQLException
     * @throws IOException
     * @throws ClassNotFoundException
     * @throws IllegalAccessException
     * @throws InstantiationException
     * @throws ProviderException
     */
    private void upgrade() throws 	SQLException,
            IOException,
            InstantiationException,
            IllegalAccessException,
            ClassNotFoundException,
            ProviderException {

        for ( ConnectionParameters aParam: _params ) {
            Connection connection = getConnection(aParam);
            try {
                Migrator migrator = new Migrator( connection, this._context, getDialect(), _versionTableName);
                migrator.initialize();
                migrator.upgrade();
            } finally {
                connection.close();
            }
        }
    }

    private void initParams(){
        //TODO: make it multiple databases.
        _params.add(  new ConnectionParameters(_migrationProps.getDatabaseUrl(), _migrationProps.getDatabaseDriver(), _migrationProps.getDatabaseUser(), _migrationProps.getDatabasePassword()));
    }
    /**
     * @return the root directory for the migration service.
     */
//    private Resource[] getUpgradeResources() {
//        try {
//            Resource[] resources = _context.getResources("classpath*:db/upgrade");
//            for(Resource res: resources){
//                System.out.println("########################################");
//                System.out.println(res.getFilename());
//                System.out.println("########################################");
//            }
//            return resources;
//        } catch (Exception e) {
//            e.printStackTrace();
//            return null;
//        }
//    }
//        return new File( this.getClass().getClassLoader().getSystemResource("").getPath() , "db");


    /**
     * @return the table that we should store the version information in.
     */
    private String getVersionTableName() {
        return _versionTableName;
    }



    /**
     * @return an instance of the dialect based on our configuration.
     * @throws IllegalAccessException
     * @throws InstantiationException
     * @throws ClassNotFoundException
     */
    private Dialect getDialect() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        String dbDriver = _migrationProps.getDatabaseDriver();
        String dialect = "mysql";
        if(dbDriver.contains("orcle"))
        {
            dialect = "oracle";
        } else if (dbDriver.contains("hsql"))
        {
            dialect = "hsql";
        }
        return getDialect(dialect);
    }

    /**
     * @param  aParam ConnectionParameters
     * @return an instance of the db connection, needs to be explicitly closed after use.
     * @throws IOException
     * @throws ClassNotFoundException
     * @throws SQLException
     * @throws ProviderException
     */
    private Connection getConnection(ConnectionParameters aParam) throws SQLException, ProviderException, ClassNotFoundException {
        //If  not using a pool, then create the connection..
        LOG.debug("using db connection to: " + aParam.getUrl() +  " user=" + aParam.getUser() + " pass=" + aParam.getPassword());

        // since database might have de-registered driver, make sure its loaded.
        Driver aDriver  = loadDriver( aParam.getUrl(), aParam.getDriver());

        LOG.debug("using db connection driver: " + aDriver);
        return  DriverManager.getConnection( aParam.getUrl(), aParam.getUser(), aParam.getPassword());
    }

    private Driver loadDriver(String url, String driverName) throws SQLException, ClassNotFoundException{
        Driver driver = null;

        LOG.debug("Try to load: [%s] [%s]", url, driverName);
        try {
            driver = DriverManager.getDriver(url);
        } catch (SQLException e) {
            LOG.debug("No existing driver for the url: [%s]", url);
        }
        if ( driver == null ) {
            try {
                Class.forName(driverName);
                driver = DriverManager.getDriver(url);
            } catch (SQLException e) {} catch (Exception e) {
                LOG.debug("Failed to load the driver: [%s]", driverName);
            }
        }
        return driver;
    }

    /**
     * @return an instance of the dialect based on our configuration.
     * @throws IllegalAccessException
     * @throws InstantiationException
     * @throws ClassNotFoundException
     */
    @SuppressWarnings("unchecked")
    private static Dialect getDialect(String dialect) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        if (DIALECTS.containsKey(dialect)) {
            return DIALECTS.get(dialect).newInstance();
        } else {
            Class<Dialect> dClass = (Class<Dialect>) Class.forName(dialect);
            return dClass.newInstance();
        }
    }
}
